#!/usr/bin/env python
# -*- mode: python; coding: utf-8; -*-
##---------------------------------------------------------------------------##
##
## Copyright (C) 1998-2003 Markus Franz Xaver Johannes Oberhumer
## Copyright (C) 2003 Mt. Hood Playing Card Co.
## Copyright (C) 2005-2010 Skomoroh
## Copyright (C) 2010-2013 Jeremy Austin-Bardo <tjaustinbardo@gmail.com>
##
## This program is free software: you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program.  If not, see <http://www.gnu.org/licenses/>.
##
##---------------------------------------------------------------------------##
"""
Initialze toolkit top level for tile/tk.
"""
__docformat__ = "restructuredtext en"
#_ = lambda x: x

import time
import tkinter
import tkinter.ttk
import logging

LOGGER = logging.getLogger('PySolFC.Viewlib.Toolkit')

from . import constants as vcnst
from .image import load_image
from .widget import SmartScrollbar
from ..utillib.misc import Point, Block, Size, Margin, Offset
from ..resourceng import TILE_DB
from ..settings import TITLE, WIN_SYSTEM, CONF_OPT
from ..utillib.cache import IMAGE_CACHE


class Toplevel(tkinter.Tk, object):
    """
    tile/Tk Wrapper to properly initialize and destroy application.
    """
    __game = None

    def __init__(self, **kw):
        """
        __init__([**Tk keywords**]) -> None

        Initialize tile/Tk toolkit. Verify toolkit versions. Set WIN_SYSTEM to
        tk value. Update tile/Tk styles and dialogs. Set callback method to
        properly quit a game before terminating application.
        """
        tkinter.Tk.__init__(self, **kw)

        self._assert_toolkit()

        WIN_SYSTEM = self.tk.call('tk', 'windowingsystem')

        if WIN_SYSTEM == 'aqua':
            # TkAqua auto-displays with console app bundles, so hide it
            try:
                self.tk.call('console', 'hide')
            except TclError:
                pass

        self.update_style()
        self._update_dialog()

        self.wm_protocol('WM_DELETE_WINDOW', self.wm_delete_window)

    def connect_game(self, game):
        """
        connect_app(game:Game) -> None

        Connect game to object.
        """
        self.__game = game

    def _assert_toolkit(self):
        """
        _assert_toolkit() -> None

        Verify toolkit verisions. Note method moved from winsytems module.
        """
        from sys import exit as sys_exit
        if tkinter.TkVersion < 8.4:  # Check for required unicode support
            sys_exit("{} needs Tcl/Tk 8.4 or better (you have {})".format(
                                                     TITLE, tkinter.TkVersion))

        try:
            self.tk.eval('package require tile 0.7.8')
        except tkinter.TclError:
            sys_exit("{} needs Tk/Tile 0.7.8 or better".format(TITLE))

    def _update_dialog(self):
        """
        _update_dialog() -> None

        Update toolkit dialogs for less ugly versions. Update menu, color pick,
        and file dialogs. Note method moved from winsytems module.
        """
        from ..utillib.pathdb import PATH_DB

        def eval_tcl_code(filename):
            """
            eval_tcl_code(filename) -> bool

            Set filename of tcl code to be evaluated for updating pytk methods.
            """
            path = PATH_DB.find_file(filename, subdirs=['tcl'],
                                        do_raise=False)
            if path is not None:
                try:
                    self.tk.evalfile(path)
                    return True
                except tkinter.TclError:
                    pass

        eval_tcl_code('menu8.4.tcl')
        eval_tcl_code('clrpick8.5.tcl')
        if eval_tcl_code('fsdialog8.5.tcl'):
            import tkinter.filedialog
            tkinter.filedialog.Open.command = 'ttk::getOpenFile'
            tkinter.filedialog.SaveAs.command = 'ttk::getSaveFile'
            #tkFileDialog.Directory.command = 'ttk::chooseDirectory'

    def update_style(self):
        """
        update_style() -> None

        Update ttk Styles. Sets tile theme to config option. Sets Tlabel styles
        for canvas font config options. Note method moved from winsytems module.
        """
        style = tkinter.ttk.Style(self)
        style.theme_use(CONF_OPT['wm_theme'])
        color = style.lookup('.', 'background')
        if color:
            self.tk_setPalette(color)

        get_font = Toplevel._font_by_name
        style.configure('TLabel', font=get_font('default'))
        style.configure('npsSmall.TLabel', font=get_font('small'))
        style.configure('npsTip.TLabel', font=get_font('small'), borderwidth=1,
                        relief=vcnst.SOLID, justify=vcnst.LEFT,
                        foreground='black', background='#ffffe0')
        style.configure('npsFixed.TLabel', font=get_font('fixed'))
        style.configure('npsLarge.TLabel', font=get_font('large'))
        style.configure('npsUri.TLabel', font=get_font('fixed'),
                        foreground='blue', cursor='hand2')
        style.configure('npsDialog.TLabel', anchor=vcnst.ANCHOR_CENTER,
                        compound='left')
        style.configure('npsDialog.TButton', width=10)

        if CONF_OPT['wm_theme'] == 'clam':
            style.configure('TLabelframe', labeloutside=False,
                            labelmargins=(8, 0, 8, 0))

    @staticmethod
    def _font_by_name(name):
        """
        Return font style of given name.

        Get font value for given name from config options and compose value for
        font keyword.

        *note:*
          Replaced font method with a tuple value as font method seems broken.

        .. \_font_by_name(name:str)
        """
        font = CONF_OPT['fonts'][name]
        family, size = font[:2]
        return (family.title(), int('-{}'.format(size)), ' '.join(font[2:]),)

    def update(self):
        """
        update() -> None

        Augment tk update to also update game.canvas during a game.
        """
        game = self.__game
        if game is not None:
            old_busy = game.busy
            game.busy = True
            if game.canvas:
                game.canvas.update()
            super(Toplevel, self).update()
            game.busy = old_busy
        else:
            super(Toplevel, self).update()

    def sleep(self, seconds):
        """
        Wait for given seconds.

        .. _sleep(seconds:int) -> None

        *Parameters*:
          seconds : int
            num of seconds to sleep
        """
        if seconds > 0:
            time.sleep(seconds)

    def wm_map(self):
        """
        wm_map() -> None

        Map a window when state is iconic. Note method from TkUtil.
        """
        if self.wm_state() != "iconic":
            self.wm_deiconify()

    def quit(self):
        """
        quit() -> None

        Augment to call quit on self after idle.
        """
        self.after_idle(super(Toplevel, self).quit)

    def wm_set_cursor(self, cursor=None):
        """
        wm_set_cursor([cursor:str=self['cursor']])

        Set cursor on *canvas* when set.
        """
        cursor = cursor or self['cursor']
        if self.__game is not None and self.__game.canvas:
            self.__game.canvas.config(cursor=cursor)

    def wm_delete_window(self):
        """
        wm_delete_window() -> None

        Call quit on self after game has quit.
        """
        game = self.__game
        if game is not None:
            game.demo_stop()
            game.quit()  # Game method calls self.quit
        else:
            super(Toplevel, self).quit()

    def wm_save_state(self):
        """
        Save current window state

        .. wm_save_state() -> None
        """
        wm_state = self.wm_state()
        if wm_state == 'zoomed':  # Windows only
            CONF_OPT['wm_maximized'] = True
        elif wm_state == 'normal':
            CONF_OPT['wm_maximized'] = False


OVERLAY_ITEM = 'OverlayItem'
BACKGROUND_ITEM = 'BackgroundItem'


class Canvas(tkinter.Canvas, object):
    """
    Game canvas to display card/tiles and annotations.
    """

    def __init__(self, parent, *args, **kwargs):
        """
        Initialize Canvas.
        """
        self.__scrollbar = {'horz': None, 'vert': None,}
        kwargs.setdefault('relief', tkinter.SUNKEN)
        kwargs.setdefault('highlightthickness', 0)
        kwargs.setdefault('bd', 1)
        kwargs.setdefault('takefocus', 1)
        super(Canvas, self).__init__(parent, *args, **kwargs)
        self.grid(padx=1, pady=1, row=1, column=1, sticky='news')
        parent.grid_columnconfigure(1, weight=1)
        parent.grid_rowconfigure(1, weight=1)
        self._set_view_scroll(parent)
        self.preview = False
        self.busy = False
        self._text_color = "#000000"
        self.__overlay = {'name': None, 'image': None}
        self.__background = {'id:': None, 'stretch': False,
                             'save_aspect': False, 'image': None}
        self.__background_image = None
        self._text_items = []
        # resize bg image
        self.bind('<Configure>', lambda e: self.update_background())

    def _set_view_scroll(self, parent):
        """
        Set canvas scrollbars.

        Init CanvasScrollbar that intelligently hide themselves. Set them in a
        scrollbar map to track their visibility before adjusting canvas view.
        Final set bindings to adjust with key presses.

        .. set_scrollbars(parent:Widget) -> None
        """
        xbar = SmartScrollbar(parent, takefocus=0, orient='horizontal')
        self.config(xscrollcommand=xbar.set)
        xbar.config(command=self.xview)
        xbar.grid(row=2, column=1, sticky='we')
        xbar.grid_remove()

        ybar = SmartScrollbar(parent, takefocus=0, orient='vertical')
        self.config(yscrollcommand=ybar.set)
        ybar.config(command=self.yview)
        ybar.grid(row=1, column=2, sticky='ns')
        ybar.grid_remove()

        self.__scrollbar = {'horz': xbar, 'vert': ybar,}
        self._set_view_bind()

    def xview(self, *args):
        """
        Adjust canvas xview scroll.

        When horizontal scrollbar is shown call next method with args sequence.
        Else call next method without args sequence.

        .. xview(*scroll/move view args*) -> *
        """
        if self.__scrollbar['horz'].is_shown:
            return super(Canvas, self).xview(*args)
        return super(Canvas, self).xview()

    def yview(self, *args):
        """
        Adjust canvas yview.

        When vertical scrollbar is shown call next method with args sequence.
        Else call next method without args sequence.

        .. yview(*scroll/move view args*) -> *
        """
        if self.__scrollbar['vert'].is_shown:
            return super(Canvas, self).yview(*args)
        return super(Canvas, self).yview()

    def _set_view_bind(self):
        """
        Set canvas view bindings.

        Set keypress for adjusting canvas view. FIX-ME KeyPress broken ??
          - <left> x scroll -1 unit
          - <right> x scroll +1 unit
          - <up> y scroll -1 unit
          - <down> y scroll +1 unit
          - <page up> y scroll -1 page
          - <page down> y scroll +1 page
          - <home> y moveto 0
          - <end> y moveto 1
        Set mousewheel with Linux to adjust y view 5 units.

        .. \_set_view_bind() -> None
        """
        self.bind('<KeyPress-Left>',
                    lambda e: self.xview('scroll', -1, 'unit'))
        self.bind('<KeyPress-Right>',
                    lambda e: self.xview('scroll', 1, 'unit'))
        self.bind('<KeyPress-Prior>',
                    lambda e: self.yview('scroll', -1, 'page'))
        self.bind('<KeyPress-Next>',
                    lambda e: self.yview('scroll', 1, 'page'))
        self.bind('<KeyPress-Up>',
                    lambda e: self.yview('scroll', -1, 'unit'))
        self.bind('<KeyPress-Down>',
                    lambda e: self.yview('scroll', 1, 'unit'))
        self.bind('<KeyPress-Home>',
                    lambda e: self.yview('moveto', 0))
        self.bind('<KeyPress-End>',
                    lambda e: self.yview('moveto', 1))
        # mousewheel support
        if WIN_SYSTEM == 'x11':
            self.bind('<Button-4>',
                        lambda e: self.yview('scroll', -5, 'unit'))
            self.bind('<Button-5>',
                        lambda e: self.yview('scroll', 5, 'unit'))

    @property
    def _cinfo_margin(self):
        """
        Return canvas margin.

        _cinfo_margin -> Margin(x:int, y:int)
        """
        return Margin(x=20, y=20) if not self.preview else Margin(x=5, y=5)

    @property
    def _cinfo_size(self):
        """
        Return canvas size.

        _cinfo_size -> Size(w:int, h:int)
        """
        width = max(int(self.winfo_width()), int(self.cget('width')))
        height = max(int(self.winfo_height()), int(self.cget('height')))
        scrollregion = self.cget('scrollregion')
        if not scrollregion:
            return Size(w=width, h=height)
        xcoord, ycoord, swidth, sheight = [int(i) for i in scrollregion.split()]
        return Size(w=max(width, swidth - xcoord),
                    h=max(height, sheight - ycoord))

    @property
    def _cinfo_coord(self):
        """
        Return canvas NW coord.

        _cinfo_coord -> Point(x:int, y:int)
        """
        return Point(x=int(self.cget('width')) * self.xview()[0],
                     y=int(self.cget('height')) * self.yview()[0])

    @property
    def _cinfo_bbox(self):
        """
        Return canvas bound box based on viewable NW coord and size.

        _cinfo_bbox -> Block(l:int, t:int, r:int, b:int)
        """
        return Block._from_point_size(self._cinfo_coord, self._cinfo_size)

    def _get_hidden_coords(self, size):
        """
        Return cords for placing a pile off screen that will never be shown.

        _cinfo_bbox -> Block(l:int, t:int, r:int, b:int)
        """
        margin = self._cinfo_margin
        return Point(x=margin.x + size.w - 10, y=margin.y + size.h - 10)

    @property
    def background_items(self):
        """
        Return background canvas items.

        background_items -> *
        """
        return self.find_withtag('BackgroundItem')

    @property
    def overlay_items(self):
        """
        Return overlay canvas items.

        overlay_items -> *
        """
        return self.find_withtag('OverlayItem')

    @property
    def canvas_items_iter(self):
        """
        Return all canvas items, except background.

        canvas_items_iter ~> iter(*)
        """
        background = self.background_items
        return (i for i in self.find_all() if i not in background)

    def _create(self, item_type, coord, options):
        """
        Create canvas item.

        Overide to provide top-image support keeping it always on top.
        """
        LOGGER.debug('Create %s: %s', item_type, coord)
        index = super(Canvas, self)._create(item_type, coord, options)
        overlay = self.overlay_items
        if overlay:
            self.tk.call(self._w, "lower", index, overlay[0])
        return index

    def tag_raise(self, item, above_this=None):
        """
        Raise canvas items with tag/group.

        Overide to provide top-image support keeping it always on top.
        """
        LOGGER.debug('Raise %s: %s', item, above_this)
        overlay = self.overlay_items
        if above_this is None and overlay and item not in overlay:
            self.tk.call(self._w, "lower", item, overlay[0])
        else:
            self.tk.call(self._w, "raise", item, above_this)

    def tag_lower(self, item, below_this=None):
        """
        Raise canvas items with tag/group.

        Overide to provide top-image support keeping it always on top.
        """
        LOGGER.debug('Lower %s: %s', item, below_this)
        background = self.background_items
        if item == BACKGROUND_ITEM or item in background:
            self.tk.call(self._w, "lower", item)
        if below_this is None and background:
            self.tk.call(self._w, "raise", item, background[-1])
        else:
            self.tk.call(self._w, "lower", item, below_this)

    def _create_match_item(self, info, card_size):
        """
        Create match item border.

        Draw rectangle border for given info that bounds sequence between
        card/tile items. Add special tag/group used by sperate destroy match
        items method to remove borders.

         .. \_create_match_item(info:seq, card_size:Size) -> None

        *Paremeters:*
          ``info``: {sequence(Pile, Card, Card, str)}
            Piles to highlight card sequence matches.
          ``card_size``: {Size(h:number, w:number)}
        """
        pile, card1, card2, color = info
        raise_ = False

        # offset and initial coords
        if card1 is card2:                                 # highlight card
            offset = pile.getOffset(card1)
            xcoord1, ycoord1 = pile.getPosition(card1)
            xcoord2, ycoord2 = xcoord1, ycoord1
            raise_ = bool(card1 is pile.TOPCARD)           # pile only card
        else:                                              # highlight pile
            offset = pile.getOffset(card1)
            if not offset == pile.getoffset(card2):
                offset = Offset(x=0, y=0)
            xcoord1, ycoord1 = pile.getPosition(card1)
            xcoord2, ycoord2 = pile.getPosition(card2)

        # boundary include initial coords
        if offset.x != 0 and offset.y == 0:                # offset horz
            ycoord2 += card_size.height
            if card2 is pile.TOPCARD:                      # pile top card
                xcoord2 += card_size.width
            else:
                xcoord2 += offset.x
                if offset.x < 0:                           # offset left
                    xcoord1 += card_size.width
                    xcoord2 += card_size.width
        elif offset.x == 0 and offset.y != 0:              # offset vert
            xcoord2 += card_size.width
            if card2 is pile.TOPCARD:                     # pile top card
                ycoord2 += card_size.height
            else:
                ycoord2 += offset.y
                if offset.y < 0:                           # offset top
                    ycoord1 += card_size.height
                    ycoord2 += card_size.height
        else:                                              # offset none
            xcoord2 += card_size.width
            ycoord2 += card_size.height
            raise_ = True

        block = Block(l=xcoord1 - 1, t=ycoord1 - 1,
                      r=xcoord2 + 1, b=ycoord2 + 1)
        LOGGER.debug('Match: %s - %s (%s)', card1.coord, card2.coord, block)
        rect = self.create_rectangle(block, {'width': 4, 'fill': None,
                                     'outline': color, 'tags': 'MatchItem'})
        if raise_:
            self.tag_raise(rect, above_this=card2._item)

    def _destroy_match_items(self):
        """
        Destroy match item borders.

        Call delete method to remove match items group to remove borders created
        by sperate create match items method.

         .. \_destroy_match_items() -> None
        """
        self.delete('MatchItem')
        self.update_idletasks()

    def _create_no_match_item(self):
        """
        Show no match for cards.

        Draw rectangle over full canvas so it borders each edge. Color outline
        with option config not matching color. Add special tag/group used by
        sperate destroy match items method to remove borders.

        .. \_create_no_match_item() -> None
        """
        bbox, margin = self._cinfo_bbox, self._cinfo_margin
        xoffset, yoffset = 3 + margin.x, 3 + margin.y
        self.create_rectangle((bbox.l + xoffset, bbox.t + yoffset,
                        bbox.r - xoffset, bbox.b - yoffset),
                        {'width': 6, 'fill': None, 'tags': 'MatchItem',
                        'outline': CONF_OPT['colors']['none']})

    def set_tablesize(self, size):
        """
        Set canvas table size for game.

        Set table size required for game from given size. .

        .. set_tablesize(size:seq(int, int)) -> None
        """
        assert isinstance(size, (list, tuple)) and len(size) == 2
        autoscale = CONF_OPT['scale_auto']
        LOGGER.debug('Canvas size: %s autoscale: %s', size, autoscale)
        margin = self._cinfo_margin
        width, height = size
        region = Block(l=-margin.x, t=-margin.y, r=margin.x, b=margin.y)

        # add scrolls
        if self.preview or not autoscale:
            region = region._replace(r=region.r + width, b=region.b + height)

        # add margins
        width += 2 * margin.x
        height += 2 * margin.y

        self.config(width=width, height=height, scrollregion=region)

    def set_textcolor(self, color=None):
        """
        Set canvas text item color.

        When given color, config text items fill color with value. Else config
        with black or white fill based on background color luminance.

        .. set_textcolor(color:str) -> None
        """
        if color is None:
            bg_color = self.cget('bg')
            if (not isinstance(bg_color, str) or bg_color[0] != "#" or
                len(bg_color) != 7):
                return
            assert bg_color[0] == "#" and len(bg_color) == 7
            luminance = sum(int(bg_color[i:i + 2], 16) * l
                                for i, l in ((1, 0.212671,), (3, 0.715160,),
                                             (5, 0.072169,),)) // 255
            LOGGER.debug('%s / %s luminance', color, luminance)
            color = ('#000000', '#ffffff') [luminance < 0.3]
        if self._text_color != color:
            self._text_color = color
            for item in self.find_withtag('TextItem'):  #_text_items:
                self.itemconfig(item, fill=self._text_color)

    def set_background(self, tile_id, force=False):
        """
        Set canvas background.

        When given tile_id not first tile, load image value of tile resource and
        update mapping tile resource values. Else set canvas background color
        to value of tile resource color and unset mapping image. Return success.

        .. set_background(tile_id:int, force:bool=False) -> bool
        """
        try:
            tileset = TILE_DB[tile_id]
        except KeyError:
            return False
        if tileset['error']:
            return False
        if not force and tile_id == self.__background['id']:
            return True
        self.__background['id'] = tile_id
        if tile_id > 0:
            LOGGER.debug('setTile: %s', tileset['filename'])
            try:
                self.__background['image'] = load_image(tileset['filename'])
            except:
                tileset['error'] = True
                return False
            self.__background.update({'stretch': tileset['stretch'],
                                     'save_aspect': tileset['save_aspect']})
            self.config(background=CONF_OPT['colors']['table'])
        else:
            self.config(background=tileset['color'])
            self.__background['image'] = None
        self.update_background()
        return True

    def update_background(self):
        """
        Update background image.

        Call delete method to destroy background. With mapping, create
        background from map values below game on canvas. Set tag/group for
        background items to manage background seperately.

        *note:*
          An unset map will expose the canvas bg color.

        .. update_background() -> None
        """
        LOGGER.debug('Set BG: %s', self.__background['image'] or 'color')

        self.delete(BACKGROUND_ITEM)
        if self.__background['image'] is None:  # keep canvas solid bg color
            self.set_textcolor()
            return
        image, screen = self.__background['image'], self._cinfo_size
        xcoord, ycoord = [-c for c in self._cinfo_margin]
        kwargs = {'anchor': vcnst.ANCHOR_NW, 'tags': BACKGROUND_ITEM}
        if self.__background['stretch']:
            image.stretch(screen, save_aspect=self.__background['save_aspect'])
            kwargs['image'] = image
            self.create_image((xcoord, ycoord,), kwargs)
        else:
            kwargs['image'] = image
            for xcoord_ in range(xcoord, screen.w, image.width()):
                for ycoord_ in range(ycoord, screen.h, image.height()):
                    self.create_image((xcoord_, ycoord_,), kwargs)
        self.tag_lower(BACKGROUND_ITEM)
        self.__background_image = image
        return True

    def set_overlay(self, title=None):
        """
        Create/destroy overlay image.

        When given title, create overlay for title centered above game on
        canvas. Set tag/group for overlay items to manage overlay seperately.
        When not given call delete method to destroy overlay.

        .. set_overlay(title:str=None) -> None
        """
        if title is None:
            self.delete(OVERLAY_ITEM)
            return
        if self.__overlay['name'] == title and self.find_withtag(OVERLAY_ITEM):
            return
        self.delete(OVERLAY_ITEM)
        self.__overlay['name'] = title
        self.__overlay['image'] = image = IMAGE_CACHE.choice(title)
        self.create_image(((self._cinfo_size.w - image.width()) / 2,
                         (self._cinfo_size.h - image.height()) / 2,),
                         image=image, anchor=vcnst.ANCHOR_NW, tags=OVERLAY_ITEM)
        self.tag_raise(OVERLAY_ITEM)

    def locate(self, pile, event):
        """
        Locate event in canvas items.

        Call find closest method. Return card index in pile of matching item.

        .. locate() -> None
        """
        items = list(self.find_closest(event.x, event.y))
        for item in items:
            for index, card in enumerate(pile.cards):
                if str(card._item) == str(item):
                    return index

    def delete(self, item):
        """
        Delete canvas tag/group.

        Overide to also reset internal mapping for overlay item. Call next
        method with item to remove tag/group from canvas.

        .. delete(item:str) -> None
        """
        if item == OVERLAY_ITEM:
            self.__overlay = {'name': None, 'image': None}
        super(Canvas, self).delete(item)

    def reset(self):
        """
        Reset canvas items.

        Call delete method on canvas items leaving only background items.

        .. reset() -> None
        """
        for item in self.canvas_items_iter:
            self.delete(item)

    def show(self, item):
        """
        Show canvas tag/group.

        Config canvas item state normal.

        .. show(item:str) -> None
        """
        if item == 'game':
            for item in self.canvas_items_iter:
                self.itemconfig(item, state='normal')
        self.itemconfig(item, state='normal')

    def hide(self, item):
        """
        Hide canvas tag/group.

        Config canvas item state hidden.

        .. hide(item:str) -> None
        """
        if item == 'game':
            for item in self.canvas_items_iter:
                self.itemconfig(item, state='hidden')
        self.itemconfig(item, state='hidden')

    def disable(self, item):
        """
        Disable canvas tag/group.

        Config canvas item state disable.

        .. disable(item:str) -> None
        """
        if item == 'game':
            for item in self.canvas_items_iter:
                self.itemconfig(item, state='disable')
        self.itemconfig(item, state='disable')